#include "src/scene/photonMapping.h"

#include "src/scene/scene.h"
#include "src/scene/light.h"
#include "src/scene/hbb/tree.h"

#include "src/defines/functions.h"

#include <iostream>
#include <stdlib.h>

#define MAPS_START 0


#define ABSORPTION 0
#define REFLEXION 1
#define REFRACTION 3
#define DIFFUSION 2

#define OUT_FROM_LIGHT 0
#define FIRST_REFRACTION 1
#define REFLEXION_AFTER_REFRACTION 2
#define ABSORB_NOW 3
#define SECOND_REFRACTION 4
#define FIRST_REFLEXION 5
#define GOING_ON_REFLEXION 6
// _causticTest = 0 - saiu da luz
// _causticTest = 1 - primeira refração (entrando)
// _causticTest = 2 - segunda refração (saindo) --- se o proximo cair no difuso, é cortado
// _causticTest = 5 - reflexão --- se o proximo cair no difuso, é cortado

PhotonMapping::PhotonMapping(Scene *_s, unsigned int _maxPhotons, unsigned int _causticMaxPhotons,
                             unsigned int _maxNearestPhotons, unsigned int _causticMaxNearestPhotons,
                             double _maxRadius, double _causticMaxRadius,
                             unsigned int _photonDepth, unsigned int _raytracingDepth,
                             unsigned short _filter, double _coneFilterConstant, bool _keepFirstBounce)
    : Render(_s, _raytracingDepth), maxPhotons(_maxPhotons), causticMaxPhotons(_causticMaxPhotons),
    maxNearestPhotons(_maxNearestPhotons), causticMaxNearestPhotons(_causticMaxNearestPhotons),
    maxRadius(_maxRadius), causticMaxRadius(_causticMaxRadius),
    photonDepth(_photonDepth), filter(_filter), coneFilterConstant(_coneFilterConstant),
    keepFirstBounce(_keepFirstBounce), testReflexion(USE_K_VALUE)
{
    srand ( time(NULL) );
}

PhotonMapping::~PhotonMapping()
{
    clearMaps();
}

bool PhotonMapping::emitPhotons(bool _doItAgain)
{
//    print();

    if(_doItAgain)
    {
        clearMaps();
    }
    else if(!maps.empty())
    {
        std::cout <<"Os mapas já estão prontos!" <<std::endl;
        return false;
    }

    const std::vector<Object *> &objects = scene->getObjects();

    //photon map global
    maps.push_back(new PhotonMap(NULL));

    for(unsigned int i = 0; i < objects.size(); i++)
    {
        if(objects[i]->needCaustic())
            maps.push_back(new PhotonMap(objects[i]));
    }

    //vai ter que criar os mapping aqui, pra depois usar no algoritmo mesmo!

    const std::vector<Light *> &lights = scene->getLights();

    #if DEBUG
    if(lights.size() > 2) std::cout <<"Tem mais de uma luz!" <<std::endl;
    #endif //DEBUG


    for(unsigned int j = MAPS_START; j < maps.size(); j++)
    {
        std::cout << "\nMapa" << j<< ": " << std::flush;

        unsigned int maxPhotonsHere;
        if(j == 0) maxPhotonsHere = maxPhotons;
        else maxPhotonsHere = causticMaxPhotons;


        for(unsigned int i = 1; i < lights.size(); i++) // ṕegando so as luzes em ser a ambiente
        {
            Light *light = lights[i];

            EnergyColor lightEnergyColor(light->getColor(), light->getEnergy());
            unsigned int maxLightsPhotons = maxPhotonsHere;   //********************** vai ter que trocar isso!
            unsigned int n = 0;


//            #if DEBUG
//            std::cout <<"A luz tem energia de "; lightEnergyColor.print();
//            #endif //DEBUG

//            #pragma omp parallel
            while(n++ < maxLightsPhotons){
                //verificar se bate
                Vector3D point, direction;
                light->generatePhotonPointDirection(&point, &direction, maps[j]->getObject());

//                #if DEBUG
//                std::cout <<"\n\n------------------------\n" << std::endl;
//                #endif //DEBUG

                testHit(Ray(point, direction), lightEnergyColor/maxLightsPhotons, photonDepth - 1, j, OUT_FROM_LIGHT);


                if( n *100 %  maxLightsPhotons == 0)
                    std::cout << "  "<< (n*100.)/maxLightsPhotons << "%" << std::flush;
            }
        }
    }



    std::cout <<"\nPossui " << maps.size() << " mapas" << std::endl;

    for(unsigned int i = 0; i < maps.size(); i++)
    {
        std::cout <<"[Mapa " << i << "] = " << maps[i]->getKdTree()->getPhotonsSize() << " photons"<< std::endl;
    }

//    #pragma omp parallel for
    for(unsigned int i = 0; i < maps.size(); i++)
    {
        maps[i]->createHeap();
    }



    std::cout <<"\nKdtree criada" << std::endl;




//
//    Vector3D testPoint(4.75,0,0);
//    std::vector<NearestPhoton> nearests = locateNearestPhotons(testPoint);
//
//
//    std::cout << " Existem " << nearests.size() << " pontos mais proximos da posiçao = "; testPoint.print();
//
//    for(unsigned int i = 0; i < nearests.size(); i++)
//    {
//        const NearestPhoton &n = nearests[i];
//
//        std::cout << i << " : \n\tdistancia = " << n.distance2 << "\n\tposiçao = "; n.photon->position.print();
//    }

    return true;
}

void PhotonMapping::testHit(const Ray &_ray, const EnergyColor &_photonEnergyColor,
                            unsigned int _depth, unsigned int _currentMap, short _causticTest)
{

    HBBTree *tree = scene->getTree();
    NearestInfo nearest;
    if(!tree->hit(_ray, &nearest))
    {
//        #if DEBUG
//        std::cout <<"\t\t\t\tNão bateu em ninguem!!!!!" <<std::endl;
//        #endif //DEBUG

        return;
    }


//    #if DEBUG
//    bool test = false;
//    if(nearest.object->getId() == 6){
//        test = true;
//        std::cout <<"\n\n\tO photon, de profundidade " << _depth << " bateu na superfície do objeto " << nearest.object->getId() << " está com o _causticTest = " << _causticTest <<std::endl;
//    }
//    #endif //DEBUG

    Photon *photon = NULL;


//        #if DEBUG
//        if(_currentMap!=0)
//        std::cout <<"\tVai ser guardado?? -------  " << keepFirstBounce << "   " << _depth << std::endl;
//        #endif //DEBUG


    if((_depth != photonDepth - 1) || keepFirstBounce){
//        #if DEBUG
//        if(_currentMap!=0)
//        std::cout <<"\tVai ser guardado??????????? " << std::endl;
//        #endif //DEBUG

        if((_currentMap == 0) || (_causticTest > ABSORB_NOW))
        {
//            #if DEBUG
//            if(_currentMap!=0)
//            std::cout <<"\tVai ser guardado? " << std::endl;
//            #endif //DEBUG

            //gerar o photon se não for totalmente especular
            if(absorbPhoton(nearest, _photonEnergyColor))
            {
//            #if DEBUG
//            if(_currentMap!=0){
//            std::cout <<"\t  SIM" << std::endl;
////            int nhe;
////            std::cin >>nhe;
//            }
//            #endif //DEBUG

        //        #if DEBUG
//                std::cout <<"\t\tEle foi guardado com energia ";_photonEnergyColor.print();
//                    int nhe;
//                    std::cin >> nhe;
        //        #endif //DEBUG
                photon = new Photon(nearest.point, -_ray.getDirection(), _photonEnergyColor);
                maps[_currentMap]->addPhoton(photon);

        //        Object *object = causticsIn(_currentMap);
        //        addToMap(photon, object);
                //não continua propagnado, caso seja um photon de caustica
        //        if(object && _currentMap != 0) return;
            }
//            #if DEBUG
//            if(_currentMap!=0){
//            std::cout <<"\t  NAO" << std::endl;
////            int nhe;
////            std::cin >>nhe;
//            }
//            #endif //DEBUG
        }
    }

//    std::cout <<"\tVai propagar? " << std::endl;
//    int nhe;
//    std::cin >> nhe;
    //ñão improta se o photon foi ou não absorviso.. ele será cortado!
    //if(_causticTest > ABSORB_NOW) return;


    //propagar o photon
    propagatePhoton(nearest, _ray, _photonEnergyColor, _depth, _currentMap, _causticTest);
}

short int PhotonMapping::russianRoulette(const NearestInfo &_nearest, const EnergyColor &_photonEnergyColor, double *_pd, double *_ps, double *_pt)
{
    const Material &material = _nearest.object->getMaterial();
    double maxDenominator = Functions::max(_photonEnergyColor.red,
                                            _photonEnergyColor.green,
                                            _photonEnergyColor.blue) * _photonEnergyColor.energy;
    switch(testReflexion)
    {
        case USE_K_VALUE: // testando com os k's para ver se é ou não especular
        {
            double maxNumerator = maxDenominator; //porque é igual

            *_pd = (maxNumerator * material.kd) / maxDenominator;
            *_ps = (maxNumerator * material.ks) / maxDenominator;
            *_pt = (maxNumerator * material.kt) / maxDenominator;

            break;
        }
        case USE_COLOR_VALUE:     // testando com o valor das cores para ver se é ou não especular
        {
            double maxNumeratorDiffuse = Functions::max(material.diffuse.red * _photonEnergyColor.red,
                                                  material.diffuse.green * _photonEnergyColor.green,
                                                  material.diffuse.blue * _photonEnergyColor.blue) * _photonEnergyColor.energy;
            double maxNumeratorSpecular = Functions::max(material.specular.red * _photonEnergyColor.red,
                                                  material.specular.green * _photonEnergyColor.green,
                                                  material.specular.blue * _photonEnergyColor.blue) * _photonEnergyColor.energy;
            double maxNumeratorTransmission = Functions::max(material.specular.red * _photonEnergyColor.red,
                                                  material.specular.green * _photonEnergyColor.green,
                                                  material.specular.blue * _photonEnergyColor.blue) * _photonEnergyColor.energy;

            *_pd = (maxNumeratorDiffuse) / maxDenominator;
            *_ps = (maxNumeratorSpecular) / maxDenominator;
            *_pt = (maxNumeratorTransmission) / maxDenominator;

            break;
        }
        case USE_BOTH_VALUES:     // testando com uma mistura do valor das cores e dos k para ver se é ou não especular
        {
            double maxNumeratorDiffuse = Functions::max(material.diffuse.red * _photonEnergyColor.red,
                                                  material.diffuse.green * _photonEnergyColor.green,
                                                  material.diffuse.blue * _photonEnergyColor.blue) * _photonEnergyColor.energy;
            double maxNumeratorSpecular = Functions::max(material.specular.red * _photonEnergyColor.red,
                                                  material.specular.green * _photonEnergyColor.green,
                                                  material.specular.blue * _photonEnergyColor.blue) * _photonEnergyColor.energy;
            double maxNumeratorTransmission = Functions::max(material.specular.red * _photonEnergyColor.red,
                                                  material.specular.green * _photonEnergyColor.green,
                                                  material.specular.blue * _photonEnergyColor.blue) * _photonEnergyColor.energy;

            *_pd = (maxNumeratorDiffuse * material.kd) / maxDenominator;
            *_ps = (maxNumeratorSpecular * material.ks) / maxDenominator;
            *_pt = (maxNumeratorTransmission * material.kt) / maxDenominator;

            break;
        }
    }


    double xi = (rand()%100)/100.0;//numeros aleatorios entre [0, 1]

    //se tiver entre 0 e D
    if(xi < *_pd) return DIFFUSION;

    //se tiver entre D e S
    if(xi < *_ps + *_pd) return REFLEXION;

    //se tiver entre S e T
    if(xi < *_pt + *_ps + *_pd) return REFRACTION;

//    std::cout <<"kt = " << *_pt << "  |  ks "<<*_ps<< "  |  kd "<< *_pd << "---------------- xi " << xi << std::endl;

    return ABSORPTION;
}

short int PhotonMapping::russianRoulette(const NearestInfo &_nearest)  const
{
    const Material &material = _nearest.object->getMaterial();


    double xi = (rand()%100)/100.0;//numeros aleatorios entre [0, 1]

    //se tiver entre 0 e D
    if(xi < material.kd) return DIFFUSION;

    //se tiver entre D e S
    if(xi < material.ks + material.kd) return REFLEXION;

    //se tiver entre S e T
    if(xi < material.kt + material.ks + material.kd) return REFRACTION;


//    std::cout <<"kt = " << material.kt << "  |  ks "<< material.ks << "  |  kd "<< material.kd << "---------------- xi " << xi << std::endl;

    return ABSORPTION;
}

bool PhotonMapping::absorbPhoton(const NearestInfo &_nearest, const EnergyColor &_photonEnergyColor)
{
    const Material &material = _nearest.object->getMaterial();
    double maxDenominator = Functions::max(_photonEnergyColor.red,
                                            _photonEnergyColor.green,
                                            _photonEnergyColor.blue) * _photonEnergyColor.energy;
    switch(testReflexion)
    {
        case USE_K_VALUE: // testando com os k's para ver se é ou não especular
        {
            double maxNumerator = maxDenominator * material.kd;

//            if(!material.kd) std::cout << "material.kd = " << material.kd << "  maxDenominator = "  << maxDenominator << "  maxNumerator = " << maxNumerator << std::endl;
            if( (fabs(maxNumerator / maxDenominator) < ERROR) || material.kd < ERROR )
                return false;

            break;
        }
        case USE_COLOR_VALUE:     // testando com o valor das cores para ver se é ou não especular
        {
            double maxNumerator = Functions::max(material.diffuse.red * _photonEnergyColor.red,
                                                  material.diffuse.green * _photonEnergyColor.green,
                                                  material.diffuse.blue * _photonEnergyColor.blue) * _photonEnergyColor.energy;

            if( fabs(maxNumerator / maxDenominator) < ERROR )
                return false;

            break;
        }
        case USE_BOTH_VALUES:     // testando com uma mistura do valor das cores e dos k para ver se é ou não especular
        {
            double maxNumerator = Functions::max(material.diffuse.red * _photonEnergyColor.red,
                                                  material.diffuse.green * _photonEnergyColor.green,
                                                  material.diffuse.blue * _photonEnergyColor.blue) * _photonEnergyColor.energy * material.kd;

            if( (fabs(maxNumerator / maxDenominator) < ERROR) || material.kd < ERROR )
                return false;

            break;
        }
    }

    return true;
}

void PhotonMapping::propagatePhoton(const NearestInfo &_nearest, const Ray &_ray,
                                    const EnergyColor &_photonEnergyColor, unsigned int _photonDepth,
                                    /*Photon *photon, */unsigned int _currentMap, short _causticTest)
{
    if(_photonDepth == 0)
        return;

    const Material &material = _nearest.object->getMaterial();

//    bool test = false;
//    if(_nearest.object->getId() == 6) test = true;

    double pd, ps, pt;
    Ray newRay;
    Object *newInsideOf = _ray.isInsideOf();
    EnergyColor newPhotonEnergyColor;
    // tenho que testar a roleta russa, ver se ele vai mesmo propagar ou não

    // tenho que ver onde que está batando
    // decidir pra onde o novo photon vai
    // e com qual cor ele está saindo
    //   tem que ver como faz pra usar os valores calculados antes.

    // verificar se é reflexão / refração / difusão
    // tem que manuipular o raio tmb (de dentro de qual objeto que ele veio)

    switch(russianRoulette(_nearest, _photonEnergyColor, &pd, &ps, &pt))
    {
        case ABSORPTION:
        {
//            #if DEBUG
////            if(_currentMap!=0){
//            std::cout <<"\t ---- ABSORVEU! " << std::endl;
////            int nhe;
////            std::cin >>nhe;
////            }
//            #endif //DEBUG
//            #if DEBUG
////            if (test)
//                std::cout <<"\t\t\tEle foi absorvido completamente no objeto " << _nearest.object->getId() <<std::endl;
//            #endif //DEBUG
            return;
            break;
        }
        case REFLEXION:
        {
//            #if DEBUG
////            if(_currentMap!=0){
//            std::cout <<"\t ---- Refletiu! " << std::endl;
////            int nhe;
////            std::cin >>nhe;
////            }
//            #endif //DEBUG

            newRay = reflectedRay(_nearest.point, _nearest.normal, _ray.getDirection(), newInsideOf);

            Color objectColor =  material.specular;
            if(material.useTexture())
            {
                if(material.texture->coloringType == REPLACE)
                    objectColor = _nearest.texture;
                else
                    objectColor = objectColor * _nearest.texture;
            }

//            double reflectance = 1.0;
//            if( (material.kt != 0 && material.refractiveIndex != 0) ){
//
//                double n1, n2;
//                findN1N2(&n1, &n2, newInsideOf, _nearest.object);
//                reflectance = findReflectance(_nearest.normal, _ray.getDirection(), n1, n2);
//
//                //em caso de o raio não entrar por causa do angulo, conta so com a reflexao
//                if(reflectance == -1)
//                    reflectance = 1.0; //assim, não influenciará no resultado a seguir
//            }

            //não importa de onde o raio venha... vai continuar na mesma.
            newPhotonEnergyColor = (_photonEnergyColor * objectColor) * ps;// * reflectance;
//            newPhotonEnergyColor = (_photonEnergyColor * objectColor * material.ks) * ps;



            if(_currentMap != 0)
            {
                switch(_causticTest)
                {
                    case OUT_FROM_LIGHT:
                        _causticTest = FIRST_REFLEXION;
                        break;
                    case FIRST_REFLEXION:
                        _causticTest = GOING_ON_REFLEXION;
                        break;
                    case GOING_ON_REFLEXION:
                        _causticTest = GOING_ON_REFLEXION;
                        break;
                    case REFLEXION_AFTER_REFRACTION:
                        _causticTest = REFLEXION_AFTER_REFRACTION;
                        break;
                    case FIRST_REFRACTION:
                        _causticTest = REFLEXION_AFTER_REFRACTION;
                        break;
                    case SECOND_REFRACTION:
                        _causticTest = FIRST_REFLEXION;
                        break;
                    default:
                        std::cout <<"Caiu num caso que não existe" << std::endl;
                        break;
                }
            }

//            #if DEBUG
////            if (test)
//                std::cout <<"\t\t\tEle foi refletido saindo do objeto " << _nearest.object->getId() << " com o teste " << _causticTest << " com a cor "; newPhotonEnergyColor.print();
//            #endif //DEBUG
            break;
        }
        case REFRACTION:
        {
//            #if DEBUG
////            if(_currentMap!=0){
//            std::cout <<"\t ---- Refratou! " << std::endl;
////            int nhe;
////            std::cin >>nhe;
////            }
//            #endif //DEBUG

            double n1, n2;

            newInsideOf = findN1N2(&n1, &n2, newInsideOf, _nearest.object);

            bool isTransmitted;
            newRay = transmittedRay(_nearest.point, _nearest.normal, _ray.getDirection(), n1, n2, &isTransmitted, newInsideOf);

            if(!isTransmitted) return;


            Color objectColor =  material.specular;
            if(material.useTexture())
            {
                if(material.texture->coloringType == REPLACE)
                    objectColor = _nearest.texture;
                else
                    objectColor = objectColor * _nearest.texture;
            }

//            double reflectance = findReflectance(_nearest.normal, _ray.getDirection(), n1, n2);

            newPhotonEnergyColor = (_photonEnergyColor  * objectColor) * pt;// * reflectance;
//            newPhotonEnergyColor = (_photonEnergyColor * objectColor * material.kt) * pt;


            if(_currentMap != 0)
            {
                switch(_causticTest)
                {
                    case OUT_FROM_LIGHT:
                        _causticTest = FIRST_REFRACTION;
                        break;
                    case FIRST_REFLEXION:
                        _causticTest = FIRST_REFRACTION;
                        break;
                    case GOING_ON_REFLEXION:
                        _causticTest = FIRST_REFRACTION;
                        break;
                    case REFLEXION_AFTER_REFRACTION:
                        _causticTest = SECOND_REFRACTION;
                        break;
                    case FIRST_REFRACTION:
                        _causticTest = SECOND_REFRACTION;
                        break;
                    case SECOND_REFRACTION:
                        _causticTest = FIRST_REFRACTION;
                        break;
                    default:
                        std::cout <<"Caiu num caso que não existe" << std::endl;
                        break;
                }
            }

//            #if DEBUG
////            if (test)
//                std::cout <<"\t\t\tEle foi refratado saindo do objeto " << _nearest.object->getId() << " com o teste " << _causticTest << " com a cor "; newPhotonEnergyColor.print();
//                std::cout << "\t\t\t\treflectance = " << reflectance << "\n\t\t\t\tpt = " << pt << "\n\t\t\t\tcor antes = "; _photonEnergyColor.print();
//                std::cout << "cor do objeto = "; objectColor.print();
//            #endif //DEBUG
            break;
        }
        case DIFFUSION:
        {
//            #if DEBUG
////            if(_currentMap!=0){
//            std::cout <<"\t ---- DIFUNDIU! " << std::endl;
////            int nhe;
////            std::cin >>nhe;
////            }
//            #endif //DEBUG

            if(_currentMap != 0)
            {
//                std::cout <<"Chegou num difuso, em caustics.. não pode!" << std::endl;
//                int nhe;
//                std::cin >> nhe;
                return;
            }

            newRay = diffusedRay(_nearest.object, _nearest.point, _nearest.normal, _ray.getDirection(), newInsideOf);


            Color objectColor =  material.diffuse;
            if(material.useTexture())
            {
                if(material.texture->coloringType == REPLACE)
                    objectColor = _nearest.texture;
                else
                    objectColor = objectColor * _nearest.texture;
            }

            newPhotonEnergyColor = (_photonEnergyColor * objectColor) * pd;
//            newPhotonEnergyColor = (_photonEnergyColor * objectColor * material.kd) * pd;


//
//            {
//                switch(_causticTest)
//                {
//                    case ABSORB_NOW:
//                        return;
//                        break;
//                    case OUT_FROM_LIGHT:
//                        return;
//                        break;
//                    case FIRST_REFLEXION:
//                        _causticTest = ABSORB_NOW;
//                        break;
//                    case GOING_ON_REFLEXION:
//                        _causticTest = ABSORB_NOW;
//                        break;
//                    case REFLEXION_AFTER_REFRACTION:
//                        return;
//                        break;
//                    case FIRST_REFRACTION:
//                        return;
//                        break;
//                    case SECOND_REFRACTION:
//                        _causticTest = ABSORB_NOW;
//                        break;
//                    default:
//                        std::cout <<"Caiu num caso que não existe" << std::endl;
//                        break;
//                }
//            }

//            #if DEBUG
////            if (test)
//                std::cout <<"\t\t\tEle foi difundido saindo do objeto " << _nearest.object->getId() << " com o teste " << _causticTest << " com a cor "; newPhotonEnergyColor.print();
//                std::cout <<"\t\t\tRaio origem = ";newRay.getOrigin().print();
//                std::cout <<"\t\t\t\tdireção = ";newRay.getDirection().print();
//            #endif //DEBUG
            break;
        }
    }


//    if(photon) photon->goingTo = newRay.getDirection();

//    reflectedRay(_nearest.point, _nearest.normal, _ray.getDirection(), _goingToObject)

    testHit(newRay, newPhotonEnergyColor, _photonDepth - 1, _currentMap, _causticTest);
}

void PhotonMapping::drawPhotons() const
{
    for(unsigned int i = 0; i < maps.size(); i++)
    {
        maps[i]->getKdTree()->drawPhotons();
    }
}

void PhotonMapping::clearMaps()
{
    for(unsigned int i = maps.size(); i > 0; i--)
    {
        std::cout <<"mapa i - 1 = " << i -1 << std::endl;
        delete maps[i - 1];
    }
    maps.clear();
}

//void PhotonMapping::addToCaustic(Object *_obj, Photon *_p)
//{
//    for(unsigned int i = 1; i < maps.size(); i++)
//    {
//        if(maps[i]->getObject() == _obj)
//            maps[i].addPhoton(_p);
//    }
//}

Color PhotonMapping::calculate(const Ray &_ray, unsigned int _depth, bool _onlyPhotons) const
{
//    #if DEBUG
//    if(inRefraction)
//    std::cout << "CALCULANDO! " << std::endl;
//    #endif //DEBUG

    //adiciona mais nada
    if(_depth == 0)
        return Color(0.0, 0.0, 0.0, 0.0);

//    #if DEBUG
//    if(inRefraction)
//    std::cout << "....CALCULANDO! " << std::endl;
//    #endif //DEBUG

    //achando o ponto mais proximo
    NearestInfo nearest;// = findNearest(_ray);

    if(!scene->getTree()->hit(_ray, &nearest))
        return scene->getBackgroundColor();

//    #if DEBUG
//    if(inRefraction)
//    std::cout << "........ CALCULANDO! bateu no " << nearest.object->getId() << std::endl;
//    #endif //DEBUG

    return findColor(nearest, _ray.getDirection(), _depth, _ray.isInsideOf(), _onlyPhotons);
}

Color PhotonMapping::findColor(const NearestInfo &_nearest, const Vector3D &_incident, unsigned int _depth, Object *_wasInsideIn, bool _onlyPhotons) const
{

    #if DEBUG
        if(!_incident.isNormalized()) std::cout << "vetor incidente não é normalizado" << std::endl;
        if(!_nearest.normal.isNormalized()) std::cout << "vetor normal não é normalizado" << std::endl;
    #endif //DEBUG


    //!existem n casos
    //!- o raio está vindo do ar e bate em um objeto opaco (mais usual)
    //!     . reflexao
    //!- o raio está vindo do ar e bate em um objeto transparente/translucido
    //!     . reflexao e refração (proporcionais à medida de reflectancia)
    //!         - a normal NAO é invertida
    //!- o raio está vindo de um objeto transparente/translucido e vai para o ar
    //!     . refração e reflexao
    //!         - a normal É invertida
    //!- o raio está vindo de um objeto transparente/translucido e bate em outro objeto opaco (NAO IMPLEMENTADA)
    //!     . reflexçao
    //!- o raio está vindo de um objeto transparente/translucido e bate em outro objeto translucido (NAO IMPLEMENTADA)
    //!     . reflexçao e refração


    Color color(0,0,0,0);
    Material nearestMaterial = _nearest.object->getMaterial();
    switch(russianRoulette(_nearest))
    {
        case DIFFUSION:
        {
//    #if DEBUG
//    if(inRefraction){
//            std::cout <<"\n\nEscolheu difusão, na profundidade " << _depth << std::endl;
//            if(_wasInsideIn){
//                std::cout <<"\tE estava no objeto " << _wasInsideIn->getId() << std::endl;
//            }
//            else std::cout <<"\tE estava no ar" << std::endl;
//            std::cout <<"\tE bateu no objeto " << _nearest.object->getId() << std::endl;
//
//    }
//    #endif //DEBUG

            //acaba com o raio, e pega as contribuições do photon mapping
            color = radianceEstimate(_nearest.point, _nearest.normal) * nearestMaterial.kd;


//
//            if(color.red + color.green + color.blue < 0.1 ){
//
//            std::cout << "usando a cor difusa "; color.print();
//            std::cout  << std::endl << std::endl<< std::endl;
//                int nhe;
//                std::cin >> nhe;
//            }



            break;
        }
        case REFLEXION:
        {
//    #if DEBUG
//    if(inRefraction){
//            std::cout <<"\n\nEscolheu reflexão, na profundidade " << _depth << std::endl;
//            if(_wasInsideIn){
//                std::cout <<"\tE estava no objeto " << _wasInsideIn->getId() << std::endl;
//            }
//            else std::cout <<"\tE estava no ar" << std::endl;
//            std::cout <<"\tE bateu no objeto " << _nearest.object->getId() << std::endl;
//    }
//    #endif //DEBUG

            //manda o raio refletido, e faz como no raytrcing
            //veritica se tem refração, se sim tem que calcular a reflectancia

            //quando um riao é refletido, sempre permanecer dentro do objeto que estava antes
            Color reflectedColor = findReflectionColor(_nearest.point, _nearest.normal, _incident, _depth - 1, _wasInsideIn, _onlyPhotons);

            double reflectance = 1.0;
            if( (nearestMaterial.kt != 0 && nearestMaterial.refractiveIndex != 0) ){

                double n1, n2;
                findN1N2(&n1, &n2,_wasInsideIn, _nearest.object);
                reflectance = findReflectance(_nearest.normal, _incident, n1, n2);

                //em caso de o raio não entrar por causa do angulo, conta so com a reflexao
                if(reflectance == -1)
                    reflectance = 1.0; //assim, não influenciará no resultado a seguir
            }
            color = reflectedColor * nearestMaterial.ks * reflectance;


//    #if DEBUG
//    if(inRefraction){
//                std::cout <<"\t\t---Voltando na reflexão para a profundidade " << _depth  << std::endl;
//                std::cout <<"\t\t\t a cor que chega da reflexão é ";reflectedColor.print();
//                std::cout <<"\t\t\t----modificada é "; color.print();
//    }
//    #endif //DEBUG

            break;
        }
        case REFRACTION:
        {
            //manda o raio refratado, e faz como no raytrcing

            double n1, n2;
            Object *goingTo = findN1N2(&n1, &n2, _wasInsideIn, _nearest.object);

//    #if DEBUG
//
//            std::cout <<"\n\nEscolheu refração, na profundidade " << _depth << std::endl;
//            if(_wasInsideIn){
//                std::cout <<"\tE estava no objeto " << _wasInsideIn->getId() << std::endl;
//            }
//            else std::cout <<"\tE estava no ar" << std::endl;
//            if(goingTo){
//                std::cout <<"\t\te vai para o objeto " <<  goingTo->getId() << std::endl;
//            }
//            else std::cout <<"\t\te vai para o ar"  << std::endl;
//    #endif //DEBUG

            Color transmittedColor = findTransmittionColor(_nearest.point, _nearest.normal, _incident,
                                                               n1, n2, _depth - 1, goingTo, _onlyPhotons);


//    #if DEBUG
//                std::cout <<"\t\t---Voltando na refração para a profundidade " << _depth  << std::endl;
//    #endif //DEBUG

            double reflectance = findReflectance(_nearest.normal, _incident, n1, n2);

            Color reflectedColor(0,0,0,0);
            //em caso de o raio não entrar por causa do angulo, conta so com a reflexao
            if(reflectance == -1)
            {

//    #if DEBUG
//                std::cout <<"\t\t\tNão foi refratado de fato, vai mandar refletido "  << std::endl;
//    #endif //DEBUG



                reflectance = 1.0; //assim, não influenciará no resultado a seguir
                transmittedColor = Color(0,0,0,0);

                reflectedColor = findReflectionColor(_nearest.point, _nearest.normal, _incident, _depth - 1, _wasInsideIn, _onlyPhotons);


//    #if DEBUG
//                std::cout <<"\t\t---Voltando na refleao da refração para a profundidade " << _depth  << std::endl;
//                std::cout <<"\t\t\t a cor que chega da reflexão na refração é ";reflectedColor.print();
//                std::cout <<"\t\t\t----modificada é "; (reflectedColor * nearestMaterial.ks * reflectance).print();
//    #endif //DEBUG

            }
//    #if DEBUG
//            else
//            {
//                std::cout <<"\t\t\t a cor que chega da refração é ";transmittedColor.print();
//                std::cout <<"\t\t\t----modificada é "; (transmittedColor * nearestMaterial.kt * (1 - reflectance)).print();
//            }
//    #endif //DEBUG

            color = (transmittedColor * nearestMaterial.kt * (1 - reflectance)) + (reflectedColor * nearestMaterial.ks * reflectance);

//    #if DEBUG
//            int nhe;
//            std::cin >> nhe;
//    #endif //DEBUG

            break;
        }
        case ABSORPTION:
        {

//    #if DEBUG
////    if(inRefraction){
//                std::cout <<"-----------Absorveu! na profundidade " << _depth  << std::endl;
////    }
//    #endif //DEBUG
            //retorna preto!
            break;
        }
    }

    if(!_onlyPhotons)
    {
        const std::vector<Light *> lights = scene->getLights();
        HBBTree *tree = scene->getTree();

        Vector3D view = (scene->getCamera()->getEye() - _nearest.point).normalize();
        color = color + (lights[0]->getIntersectionColor(_nearest, view, tree));//ambiente
        Color lightColor(0.0, 0.0, 0.0, 0.0);

        //!colocando a cor de todas as luzes
        unsigned int lsize = lights.size();
        for(unsigned int i = 1; i < lsize; i++){//aqui ja inclui uma luz ambiente!
            lightColor = lightColor + (lights[i]->getIntersectionColor(_nearest, view, tree));

            //TODO: testar dividir a cor da luz por lsize;
        }
        color = color + (lightColor/((double)lsize - 1.0));

        if(nearestMaterial.useTexture() && (nearestMaterial.texture->coloringType == MULTIPLY))
        {
            color =
                color *
                _nearest.texture;
        }
    }


    return color.truncate();
}

EnergyColor PhotonMapping::radianceEstimate(const Vector3D &_point, const Vector3D &_normal) const
{
    std::vector<NearestPhoton> nearestPhotons = locateNearestPhotons(_point);

    //pq?????
    if(nearestPhotons.empty())
        return EnergyColor(0,0,0,1, 0);

//    std::cout << "\n\nEstimando a radiância, com " << nearestPhotons.size() << "photons" << std::endl;

    EnergyColor result(0,0,0,1,0);
    for(unsigned int i = 0; i < nearestPhotons.size(); i++)
    {
        Photon *p = nearestPhotons[i].photon;
        if( (p->direction.dotProduct(_normal)) > 0.0)
        {
            result = result + (p->color * calculateWeight(nearestPhotons[i].distance2, nearestPhotons[i].maxRadius));
        }
    }

    double density = (M_PI * nearestPhotons.front().distance2);
    if(filter == CONE_FILTER)
        density = density * (1.0 - (2.0/(3.0*coneFilterConstant)));


    return result / density;
}

double PhotonMapping::calculateWeight(double _distance2, double _maxRadius) const
{
    switch(filter)
    {
        case CONE_FILTER:
        {
            return 1.0 - (sqrt(_distance2) / (coneFilterConstant * _maxRadius));
            break;
        }
        case GAUSSIAN_FILTER:
        {
            double alpha = 0.918;
            double beta = 1.953;
            return alpha * (1.0 - ((1.0 - pow(M_E, -beta * (_distance2/(2*_maxRadius*_maxRadius))))/(1 - pow(M_E, -beta))));
            break;
        }
    }
    return 1;
}

std::vector<NearestPhoton> PhotonMapping::locateNearestPhotons(const Vector3D &_point) const
{
    std::vector<NearestPhoton> heap;
    for(unsigned int i = maps.size(); i > MAPS_START; i--)
    {
        double mapMaxRadius = maxRadius;
        unsigned int mapMaxNearestPhotons = maxNearestPhotons;

        if((i - 1 != 0) || (heap.size() == causticMaxNearestPhotons))
        {
            mapMaxRadius = causticMaxRadius;
            mapMaxNearestPhotons = causticMaxNearestPhotons;
        }
        else
        {
            heap.clear();
        }

        KdTree *kdTree = maps[i - 1]->getKdTree();
        if(kdTree->getPhotonsSize() == 0) continue;

        kdTree->locateNearestPhotons(0, mapMaxNearestPhotons, mapMaxRadius, _point, &heap);
    }

    return heap;
}

void PhotonMapping::print() const
{
    std::cout << " Photon Mapping"
              << "\n\tProfundidade do raytracing = " << depth
              << "\n\tProfundidade dos photons = " << photonDepth
              << "\n\tEmite " << maxPhotons << " photons no mapa normal e " << causticMaxPhotons << " no mapa de caustica "
              << "\n\tUsa " << maxNearestPhotons << " photons para a radiancia normal e " << causticMaxNearestPhotons << " para a da caustica "
              << "\n\tUsa " << maxRadius << " de raio para a radiancia normal e " << causticMaxRadius << " para o da caustica "
              << std::endl;

    switch(filter)
    {
        case WITHOUT_FILTER:
            std::cout << "\tNão utiliza filtro" << std::endl;
            break;
        case CONE_FILTER:
            std::cout << "\tFiltro de cone com constante " << coneFilterConstant << std::endl;
            break;
        case GAUSSIAN_FILTER:
            std::cout << "\tFiltro gaussiano" << std::endl;
            break;
    }
    switch(testReflexion)
    {
        case USE_K_VALUE:
            std::cout << "\tTestando a reflexão com K" << std::endl;
            break;
        case USE_COLOR_VALUE:
            std::cout << "\tTestando a reflexão com cor"<< std::endl;
            break;
        case USE_BOTH_VALUES:
            std::cout << "\tTestando com uma combinação dos dois valores" << std::endl;
            break;
    }

    if(keepFirstBounce)
        std::cout << "\tVai guardar a priemira batida" << std::endl;
    else
        std::cout << "\tNão vai guardar a primeira batida" << std::endl;
}
